/**
 * Copyright (C) 2008 by The Regents of the University of California
 * Redistribution of this file is permitted under the terms of the GNU
 * Public License (GPL).
 *
 * @author Junghoo "John" Cho <cho AT cs.ucla.edu>
 * @date 3/24/2008
 */

#include <cstdio>
#include <iostream>
#include <fstream>
#include "Bruinbase.h"
#include "SqlEngine.h"

using namespace std;

// external functions and variables for load file and sql command parsing 
extern FILE* sqlin;
int sqlparse(void);


RC SqlEngine::run(FILE* commandline)
{
  fprintf(stdout, "Bruinbase> ");

  // set the command line input and start parsing user input
  sqlin = commandline;
  sqlparse();  // sqlparse() is defined in SqlParser.tab.c generated from
               // SqlParser.y by bison (bison is GNU equivalent of yacc)

  return 0;
}

RC SqlEngine::select(int attr, const string& table, const vector<SelCond>& cond)
{
  RecordFile rf; 
  RecordId   rid;
  BTreeIndex btindex;	

  bool noteq(false), indexState(false);
  
  RC     rc;
  int    key, diff, temp;     
  string value;
  int    count = 0;
  
  // open the table file
  if ((rc = rf.open(table + ".tbl", 'r')) < 0) {
    fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
    return rc;
  }
  
  SelCond* iKey = NULL;
  SelCond* maxKey = NULL;
  SelCond* minKey = NULL;
  SelCond* nKey = NULL;
  SelCond* iVal = NULL;
  SelCond* maxVal = NULL;
  SelCond* minVal = NULL;
  SelCond* nVal = NULL;
  
  int gKey(0), lKey(0);
  
  for (unsigned i = 0; i < cond.size(); i++) {
	temp = atoi(cond[i].value);
	switch(cond[i].attr) {
	case 1:
		switch (cond[i].comp) {
		case SelCond::EQ:
			iKey = (SelCond*) &cond[i];
			break;
		case SelCond::NE:
			noteq = true;
			nKey = (SelCond*) &cond[i];
			break;
		case SelCond::LE:
			temp++;
		case SelCond::LT:
			if (maxKey == NULL || atoi(maxKey->value) < temp) {
				maxKey = (SelCond*) &cond[i];
				if(maxKey->comp == SelCond::LE) {
					lKey = 1;
				} else {
					lKey = 0;
				}
			}
			break;
		case SelCond::GE:
			temp--;
		case SelCond::GT:
			if (minKey == NULL || atoi(minKey->value) > temp) {
				minKey = (SelCond*) &cond[i];
				if(minKey->comp == SelCond::GE) {
					gKey = 1;
				} else {
					gKey = 0;
				}
			}
			break;
		}
		break;
	case 2:		// Value
		switch (cond[i].comp) {
		case SelCond::EQ:
			iVal = (SelCond*) &cond[i];
			break;
		case SelCond::NE:
			noteq = true;
			nVal = (SelCond*) &cond[i];
			break;
		case SelCond::LE:
			temp++;
		case SelCond::LT:
			if (maxVal == NULL || atoi(maxVal->value) < temp)
				maxVal = (SelCond*) &cond[i];
			break;
		case SelCond::GE:
			temp--;
		case SelCond::GT:
			if (minVal == NULL || atoi(minVal->value) > temp)
				minVal = (SelCond*) &cond[i];
			break;
		}
		break;
	}
}
  
  if ((btindex.open(table + ".idx", 'r')) == 0) {
	indexState = true;
	
	IndexCursor ic;
	
	if (iKey || minKey || maxKey || nKey) {
		int findKey,lowKey,highKey,notKey;
		
		if (iKey)
			findKey = atoi(iKey->value);
		else if(minKey) {
			lowKey = atoi(minKey->value);
			findKey = lowKey;
		}
		
		if(maxKey) {
			highKey = atoi(maxKey->value);
			if(!minKey) {
				findKey = highKey;
			}
		}
		
		if(nKey) 
			notKey = atoi(nKey->value);
		
		if(maxKey) {
			if((rc = btindex.locate(0, ic)) != 0) {
				btindex.close();
				return rc;
			}
		} else if(!nKey){
			if ((rc = btindex.locate(findKey, ic)) != 0) {
				btindex.close();
				return rc;
			}
		}
		
		while (btindex.readForward(ic, key, rid) == 0 ) {		   			
			if(iKey && key != findKey) continue;
			if(minKey && ((gKey==1)?(key < lowKey):(key <= lowKey))) continue;
			if(maxKey && ((lKey==1)?(key > highKey):(key >= highKey))) break;
			if(nKey && key == notKey) continue;
			
			 if ((rc = rf.read(rid, key, value)) != 0) {
				fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
				btindex.close();
				return rc;
			}

			if (iVal != NULL && value != iVal->value)		continue;
			if (maxVal != NULL &&  value >= maxVal->value)	continue;
			if (minVal != NULL && value <= minVal->value)	continue;
			if (nVal && value <= nVal->value) continue;

			count++;

			switch (attr) {
			case 1: 
			  fprintf(stdout, "%d\n", key);
			  break;
			case 2: 
			  fprintf(stdout, "%s\n", value.c_str());
			  break;
			case 3:
			  fprintf(stdout, "%d '%s'\n", key, value.c_str());
			  break;
			}
			
			if(iKey && key == findKey) break;
			if(nKey && key != notKey) break;
		}
	}
	
	if(iKey || minKey || maxKey || nKey) {
		if(attr == 4) {
			fprintf(stdout, "%d\n", count);
		}
	}
	
	if(iKey || minKey || maxKey) {
		btindex.close();
	}
  }
  
  if(!indexState || (!(iKey || minKey || maxKey))) {
	rid.pid = rid.sid = 0;
	count = 0;
	
	while (rid < rf.endRid()) {
		if ((rc = rf.read(rid, key, value)) < 0) {
		  fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
		  goto exit_select;
		}

		if (iKey != NULL && (int) key != atoi(iKey->value))	goto next_tuple;
		if (maxKey != NULL && (int) key >= atoi(maxKey->value))	goto next_tuple;
		if (minKey != NULL && (int) key <= atoi(minKey->value))	goto next_tuple;
		if (nKey && (int) key == atoi(nKey->value)) goto next_tuple;

		if (iVal != NULL && value != iVal->value)		goto next_tuple;
		if (maxVal != NULL &&  value >= maxVal->value)	goto next_tuple;
		if (minVal != NULL && value <= minVal->value)	goto next_tuple;
		if (nVal && value == nVal->value) goto next_tuple;

		count++;

		switch (attr) {
		case 1:  
		  fprintf(stdout, "%d\n", key);
		  break;
		case 2: 
		  fprintf(stdout, "%s\n", value.c_str());
		  break;
		case 3:
		  fprintf(stdout, "%d '%s'\n", key, value.c_str());
		  break;
	}
	
	next_tuple:
	++rid;
	}

	if (attr == 4) 
	  fprintf(stdout, "%d\n", count);
	
	rc = 0;

	}
	// close the table file and return
	exit_select:
	rf.close();
	return rc;
}

RC SqlEngine::load(const string& table, const string& loadfile, bool index)
{
	// Open or create the new RecordFile
	RecordFile* rf = new RecordFile(table + ".tbl", 'w');
	BTreeIndex btindex;

	if(index) {
		btindex.open(table + ".idx", 'w');
	}

	ifstream ifs;
	ifs.open(loadfile.c_str());
	  
	string buffer;
	while (ifs.good() && getline(ifs, buffer, '\n')) {
		int key;
	    string s;
		RecordId id;
		
		if (parseLoadLine(buffer, key, s) == 0) {
			if (rf->append(key, s, id) == 0) {
				if(index) {
					if( btindex.insert(key, id) != 0) 
						return RC_FILE_WRITE_FAILED;
					
				}
			} else {
				return RC_INVALID_ATTRIBUTE;
			}
		 } else {
			return RC_INVALID_ATTRIBUTE;
		}
	}
 
	btindex.close();
	rf->close();
	delete(rf);
	return 0;
}

RC SqlEngine::parseLoadLine(const string& line, int& key, string& value)
{
    const char *s;
    char        c;
    string::size_type loc;
    
    // ignore beginning white spaces
    c = *(s = line.c_str());
    while (c == ' ' || c == '\t') { c = *++s; }

    // get the integer key value
    key = atoi(s);

    // look for comma
    s = strchr(s, ',');
    if (s == NULL) {
		cout << "s == NULL" << endl;
		return RC_INVALID_FILE_FORMAT; 
	}

    // ignore white spaces
    do { c = *++s; } while (c == ' ' || c == '\t');
    
    // if there is nothing left, set the value to empty string
    if (c == 0) { 
        value.erase();
        return 0;
    }

    // is the value field delimited by ' or "?
    if (c == '\'' || c == '"') {
        s++;
    } else {
        c = '\n';
    }

    // get the value string
    value.assign(s);
    loc = value.find(c, 0);
    if (loc != string::npos) { value.erase(loc); }

    return 0;
}
